//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//     Filename: XMLTranslator.cpp		                                    //
//  Create date: April 7, 2004                                              //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  Description: This file implements the XMLTranslator class, which is     //
//                the base class for a specific group of Protocol Translator//
//                objects that communicate via XML.                         //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//       Copyright (c) 2004 - Realtime Gaming.  All rights reserved.        //
//////////////////////////////////////////////////////////////////////////////

#include <windows.h>
#include "lib.h"
#include "connect.h"
#include "XMLTranslator.h"
#include "XMLDoc.h"
#include "log.h"
#include "endian.h"
#include "stdncerr.h"

//////////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////////
XMLTranslator::XMLTranslator( Server* pServer, 
							  Connection* pParent, 
							  uint8 nGameID,
                              uint8 nNumBytesMsgLen) : 
                                             ProtocolTranslator( pServer, 
							                                     pParent, 
																 nGameID,
                                                                 nNumBytesMsgLen )
{
    m_pDoc = new XMLDoc();
	m_bEncryptedCredentials = false;
}

//////////////////////////////////////////////////////////////////////////////
// Destructor
//////////////////////////////////////////////////////////////////////////////
XMLTranslator::~XMLTranslator()
{    
    if ( m_pDoc )
    {
        delete m_pDoc;
    }
}

//////////////////////////////////////////////////////////////////////////////
// ParseIncomingMessage
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::ParseIncomingMessage( uint8* pInBuff, 
                                           int32* pIEat, 
                                           uint8* pOutBuff, 
                                           uint32 nOutBuffSize )
{
    bool8 bRetVal = false;
	String sMsgID;

	DEBUGMSG( ("\n***Incoming: %s", pInBuff) );

	// Load up XML Incoming Message
	if ( m_pDoc->LoadXML( pInBuff ) )
	{
        // Get the message ID
        if ( m_pDoc->GetSingleNodeText( TEXT("//msg_id"), m_sMsgID ) )
        {
            bRetVal = true;
        }
        else
        {
 		    // Log error into error log
		    Log::msgError( TEXT("Error, incoming XML message does not contain <msg_id> tag.") );
			
			// Set an error into the out buffer
		    ReturnErrorMessage( TEXT("ServerError"), pOutBuff, nOutBuffSize );      
        }
    }
    else
    {
		// Log error into error log
		Log::msgError( TEXT("Error, unable to parse incoming XML.") );
 		
		// Set an error into the out buffer
		ReturnErrorMessage( TEXT("ServerError"), pOutBuff, nOutBuffSize );
    }

    // Set iEat to the byte lenght of the incoming XML message.  We assume that
	//  the message is NULL terminated
    *pIEat = (strlen( (char*) pInBuff ) + 1); // add one to count the NULL

    return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
// HandleMessage
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::HandleMessage( uint8* pOutBuff, uint32 nOutBuffSize, int32* pnOutBytesUsed )
{
    // Set defaults
    bool8 bRetVal = false;
    *pnOutBytesUsed = 0;

    // These are messages that we process ourselves and thus are not sent
    //  to the game server
    
    // GetErrorString Message
    if ( m_sMsgID == "GetErrorString" )
    {
        String sID;
        if ( m_pDoc->GetSingleNodeText( TEXT("//id"), sID ) )
        {
            ReturnErrorMessage( sID, pOutBuff, nOutBuffSize );
        }
        else
        {
            ReturnErrorMessage( TEXT("ServerError"), pOutBuff, nOutBuffSize );
            Log::msgError( TEXT("Error, no ID found in GetErrorString message.") );
        }

        // Message was handled
        bRetVal = true;
    }

    // Client Locale Request
    else if ( m_sMsgID == "Locale" )
    {
        // Return locale info in the out buffer
        if ( !ReturnLocaleInfo( pOutBuff, nOutBuffSize ) )
        {
		    ReturnErrorMessage( TEXT("ServerError"), pOutBuff, nOutBuffSize );
		    Log::msgError( TEXT("Error, unable to return locale info.") );
        }

        // Message was handled
        bRetVal = true;
    }
    
    // If message was handled
    if ( bRetVal )
    {
        // Calculate bytes used in return
        *pnOutBytesUsed = strlen( (char*) pOutBuff ) + 1; // Add one for the NULL

        // Write out message in debug
        DEBUGMSG( ("\n***Outgoing: %s", pOutBuff) );
    }

    return bRetVal;
}


//////////////////////////////////////////////////////////////////////////////
// ReturnErrorMessage
// kharmon.  05-04-07.
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::ReturnErrorMessage(const String& sErrorID, const String& sMessage, uint8* pOutBuff, uint32 nOutBuffSize )
{
	bool8 bRetVal = false;
	XMLDoc doc;    

    // Sanity check
	if ( nOutBuffSize == 0 )
	{
		return false;
	}

    // Create the XML return
	if ( doc.StartNew() )
	{
		if ( doc.CreateRootNode( TEXT("game_msg") ) )
		{
			if ( doc.AddRootChildNode( TEXT("msg_id"), TEXT("Error") ) )
			{
				if ( doc.AddRootChildNode( TEXT("id"), sErrorID ) )
                {
				    if ( doc.AddRootChildNode( TEXT("error"), sMessage ) )
				    {
					    if ( doc.SendXML( pOutBuff, nOutBuffSize ) )
					    {
						    bRetVal = true;
					    }
				    }
                }
			}
		}
	}

	Log::msgError( (const uint16*)sMessage );

	return bRetVal;
}


//////////////////////////////////////////////////////////////////////////////
// ReturnErrorMessage
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::ReturnErrorMessage( const String& sErrorID, uint8* pOutBuff, uint32 nOutBuffSize )
{
    String sMessage;

    // Load the message from the string table
    if ( !m_pServer->getString( m_pParent->GetLanguage(),
								sErrorID, 
								sMessage ) )
    {
        // If we can't get the string, set the message equal to the error ID
        sMessage = sErrorID;
    }    

    // kharmon.  05-04-07.  Pass this info off to the new method for final processing.
	return ReturnErrorMessage(sErrorID, sMessage, pOutBuff, nOutBuffSize);
}


//////////////////////////////////////////////////////////////////////////////
// ReturnLoginError
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::ReturnLoginError( uint8* pOutBuff, uint32 nOutBuffSize )
{
    String sErrorID;

    ASSERT( m_nLastLoginError != 0 );

    switch( m_nLastLoginError )
    {
    case eTryLater:
        sErrorID = TEXT("ACNT_TryLater");
        break;
    case eAcntExists:
        sErrorID = TEXT("ACNT_AcntExists");
        break;
    case eMissingData:
        sErrorID = TEXT("ACNT_MissingData");
        break;
    case eBadPass:
        sErrorID = TEXT("ACNT_BadPass");
        break;
    case eNotAllowed:
        sErrorID = TEXT("ACNT_NotAllowed");
        break;
    case eBadVersion:
        sErrorID = TEXT("ACNT_BadVersion");
        break;
    case eTrackFailed:
        sErrorID = TEXT("ACNT_TrackFailed");
        break;
    case eLoggedIn:
        sErrorID = TEXT("ACNT_AlreadyLoggedIn");
        break;
    case eFunNotAvail:
        sErrorID = TEXT("FunNotAvail");
        break;
    case eExternalAuthFailed:
        sErrorID = TEXT("ExternalAuthFailed");
        break;
    default:
        ASSERT(false);
        sErrorID = TEXT("UnknownError");
        break;
    }

    return ReturnErrorMessage( sErrorID, pOutBuff, nOutBuffSize );
}

//////////////////////////////////////////////////////////////////////////////
// TranslateGenericOutError
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::TranslateGenericOutError( uint8* pOutBuff, 
									           uint32 nOutBuffSize, 
									           uint8* pGameOutBuff, 
										       uint32 nGameOutBuffSize,
											   uint8  nMsgLenSize      )
{
	bool8 bRetVal = false;

	ASSERT( pGameOutBuff[nMsgLenSize] == 'E' );

	switch( pGameOutBuff[nMsgLenSize+1] )
	{
		case LOGINERR_NOTLOGGEDIN:
			bRetVal = ReturnErrorMessage( TEXT("NoLogin"), pOutBuff, nOutBuffSize );
			break;

		case LOGINERR_BADVERSION:
			bRetVal = ReturnErrorMessage( TEXT("VersionIncorrect"), pOutBuff, nOutBuffSize );
			break;

		case LOGINERR_TRYLATER:
			bRetVal = ReturnErrorMessage( TEXT("ServerUnavail"), pOutBuff, nOutBuffSize );
			break;

		case LOGINERR_CONTACTCUSTSERVICE:
			bRetVal = ReturnErrorMessage( TEXT("ContactCS"), pOutBuff, nOutBuffSize );
			break;

		case LOGINERR_PROGERROR:
			bRetVal = ReturnErrorMessage( TEXT("GameUnavail"), pOutBuff, nOutBuffSize );
			break;

        case ERR_BADSAVEDGAME:
        {
            // kharmon.  05-04-07.  New error message for bad saved games.
	        /*
			int32 nGameNum = 0;
			Endian::ReceiveInt4( &nGameNum, &pGameOutBuff[nMsgLenSize+2] );
			String strGameNum;
            strGameNum.sprintf("%d", nGameNum);
			*/ 
			int64 nGameNum = 0;
			Endian::ReceiveInt8( &nGameNum, &pGameOutBuff[nMsgLenSize+2] );
			String strGameNum;
            strGameNum.sprintf("%I64d", nGameNum);

            String strErrorMsg;
            // Load the message from the string table
            if ( !m_pServer->getString( m_pParent->GetLanguage(),
								        TEXT("SaveGameRestoreError"), 
								        strErrorMsg ) )
            {
                // If we can't get the string, set the message equal to the error ID
                strErrorMsg = TEXT("SaveGameRestoreError");
            }

            strErrorMsg.replace("<GAMENUM>", strGameNum);

            bRetVal = ReturnErrorMessage(TEXT("SaveGameRestoreError"), strErrorMsg, pOutBuff, nOutBuffSize );

            break;
        }

		default:
			bRetVal = ReturnErrorMessage( TEXT("UnknownError"), pOutBuff, nOutBuffSize );
			break;
	}

	return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInBalance
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::TranslateInBalance( XMLDoc* pDoc, uint8* pGameInBuff,  uint32 nGameInBuffSize, uint8 nMsgLenSize )
{
	//Make sure we don't include extra junk when there's more than
	//on byte in the message length
	memset( pGameInBuff, 0, nMsgLenSize );

	// Message Size
	pGameInBuff[0] = 1 + nMsgLenSize;

	// Message ID
	pGameInBuff[nMsgLenSize] = 'm';

	// Tell the parent connection to "ignore" this message for the purposes
	//  of the timeout, since this is not a message that is generated by
	//  user action, but rather automatically by the client.
	//  ((ProxyConnection*) m_pParent)->IgnoreThisMessageForTimeout();

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutBalance
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::TranslateOutBalance( uint8* pOutBuff, uint32 nOutBuffSize, uint8* pGameOutBuff, uint32 nGameOutBuffSize, uint8 nMsgLenSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Balance") ) )
	{
		return false;
	}

	// Set initial offset
	uint32 nOffset = nMsgLenSize + 1;

	// receive balance
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//****************************************************************************
// Intnernal Helpers
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
// LoginUser
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::LoginUser( XMLDoc* pDoc, String* psPassword )
{
    // First find out if the PID and password are encrypted
	// [AO 2007-09-27] SPT-29937: Invalid values default to false
	//  TODO: Don't read encrypted and password fields (CSN-13784)
	m_bEncryptedCredentials = false;
    if ( !pDoc->GetSingleNodeBool( TEXT("//encrypted"), m_bEncryptedCredentials ) )
    {
		m_nLastLoginError = eMissingData;
        return false;
    }

    //////////////////////////////////////////////////////////////////////////
    // Get user name and password
    //////////////////////////////////////////////////////////////////////////
    String sPassword;
    if ( m_bEncryptedCredentials )
    {
		String sEncryptedLogin;
	    if ( !pDoc->GetSingleNodeText( TEXT("//pid"), sEncryptedLogin ) )
        {
			m_nLastLoginError = eMissingData;
		    return false;
	    }  
		else
		{
			m_sLogin = sEncryptedLogin;
		}

		String sEncryptedPassword;
	    if ( !pDoc->GetSingleNodeText( TEXT("//password"), sEncryptedPassword ) )
        {
			m_nLastLoginError = eMissingData;
		    return false;
	    } 
		else
		{
			sPassword = sEncryptedPassword;
		}
    }
    else
    {
	    if ( !pDoc->GetSingleNodeText( TEXT("//pid"), m_sLogin ) )
        {
			m_nLastLoginError = eMissingData;
		    return false;
	    }  

	    if ( !pDoc->GetSingleNodeText( TEXT("//password"), sPassword ) )
        {
			m_nLastLoginError = eMissingData;
		    return false;
	    } 
    }

	if ( NULL != psPassword )
	{
		*psPassword = sPassword;
	}

	//////////////////////////////////////////////////////////////////////////
	// Get Client type. CSN-7534
	//////////////////////////////////////////////////////////////////////////
	if ( !pDoc->GetSingleNodeByte( TEXT("//ClientID"), m_nClientID ) )
    {
		// Default: Unknown
		m_nClientID = 0;
	}  

	//////////////////////////////////////////////////////////////////////////
	// Get Authentication token. CSN-7075: New Authentication
	//////////////////////////////////////////////////////////////////////////
	m_sAuthenticationToken = Library::String::emptyString;
	if ( !pDoc->GetSingleNodeText( TEXT("//token"), m_sAuthenticationToken ) )
    {
		m_nLastLoginError = eMissingData;
		return false;
	}  

    //////////////////////////////////////////////////////////////////////////
    // Get the money type
    //////////////////////////////////////////////////////////////////////////
	String sMoneyType;
	if ( !pDoc->GetSingleNodeText( TEXT("//money_type"), sMoneyType ) )
	{
		m_nLastLoginError = eMissingData;
		return false;
	}
	if ( sMoneyType == TEXT("Real") )
	{
		m_bForFun = false;
	}
	else
	{
		/// [2013-07-08] Charles, Added codes checking if sMoneyType is "Play". It is given when a mobile black is connected.
		ASSERT( sMoneyType == TEXT("Fun") || sMoneyType == TEXT("Play") );
		m_bForFun = true;
	}

	// Log::msgInformation( TEXT("Logging in user[ %s ]"), (const uint16*)m_sLogin );

	// CSN-7076: New Authentication, fun login with no PID
	if ( m_sLogin.isEmpty() && m_bForFun )
    {
		// Set flag indicating that we are using the special for fun pid
        m_bUsingFunPID = true;
    }

    //////////////////////////////////////////////////////////////////////////
    // Attempt to "login", which now just means to collect our member variables
	// and parse the XML message.
    //////////////////////////////////////////////////////////////////////////
    uint8 nResult = m_pParent->ParseXMLLogin();
	if ( nResult != 0 )
    {
		Log::msgError( TEXT( "User login failed." ) );

        // Set error result
        m_nLastLoginError = nResult;
        
        // Return false
        return false;
    }

    return true;
}


//////////////////////////////////////////////////////////////////////////////
// AddLoginVersionInfo
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::AddLoginVersionInfo( uint32& nOffset, 
										  XMLDoc* pDoc, 
										  uint8*  pGameInBuff )
{
	// Get Version High Byte
	uint8 nByte;
	if ( !pDoc->GetSingleNodeByte( TEXT("//version_hi"), nByte ) )
	{
		return false;
	}
	pGameInBuff[nOffset++] = nByte;

	// Get Version Low Byte
	if ( !pDoc->GetSingleNodeByte( TEXT("//version_low"), nByte ) )
	{
		return false;
	}
	pGameInBuff[nOffset++] = nByte;

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// AddLoginPID
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::AddLoginID( uint32& nOffset, 
								 XMLDoc* pDoc, 
								 uint8*  pGameInBuff )
{	
    // serialize it
	nOffset += m_sLogin.serialize( (void*) (pGameInBuff+nOffset) );

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// AddLoginPID
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::AddPassword( uint32& nOffset, 
								 XMLDoc* pDoc, 
								 uint8*  pGameInBuff )
{	
	// First find out if the PID and password are encrypted
	// [AO 2007-09-27] SPT-29937: Handle invalid values and fix evil code
    bool8 bEncryptedCredentials = false;
    if ( !pDoc->GetSingleNodeBool( TEXT("//encrypted"), bEncryptedCredentials ) )
    {
        return false;
    }

    //////////////////////////////////////////////////////////////////////////
    // Get user name and password
    //////////////////////////////////////////////////////////////////////////
    String sPassword;
	String sEncryptedPassword;
    if ( bEncryptedCredentials )
    {
		// TODO: Don't read password field anymore (CSN-13784)

	    if ( !pDoc->GetSingleNodeText( TEXT("//password"), sEncryptedPassword ) )
        {
		    return false;
	    } 
		else
		{
			sPassword = sEncryptedPassword;
	    } 
    }
    else
    {
	    if ( !pDoc->GetSingleNodeText( TEXT("//password"), sPassword ) )
        {
		    return false;
	    } 
    }

    // serialize it
	nOffset += sPassword.serialize( (void*) (pGameInBuff+nOffset) );

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// AddLoginMachID
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::AddLoginMachID( uint32& nOffset, 
								     XMLDoc* pDoc, 
								     uint8*  pGameInBuff )
{
	if ( !pDoc->GetSingleNodeByte( TEXT("//machine_id"), m_nMachineID ) )
	{
		return false;
	}

	pGameInBuff[nOffset++] = m_nMachineID;

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// AddLoginCreditValue
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::AddLoginCreditValue( uint32& nOffset, 
								          XMLDoc* pDoc, 
								          uint8*  pGameInBuff )
{
	uint16 nCreditValue;

	if ( !pDoc->GetSingleNodeShort( TEXT("//credit_value"), nCreditValue ) )
	{
		return false;
	}

	Endian::SendInt2( (uint16) nCreditValue, (pGameInBuff + nOffset) );
	nOffset += 2;

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// AddLoginMoneyType
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::AddLoginMoneyType( uint32& nOffset, 
								        XMLDoc* pDoc, 
								        uint8*  pGameInBuff )
{
	if ( !m_bForFun )
	{
		pGameInBuff[nOffset++] = 1;
	}
	else
	{
		pGameInBuff[nOffset++] = 0;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// AddAuthenticationToken. CSN=6340: New Authentication
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::AddAuthenticationToken( uint32& nOffset, 
								             XMLDoc* pDoc, 
								             uint8*  pGameInBuff )
{
    // serialize it
	nOffset += m_sAuthenticationToken.serialize( (void*) (pGameInBuff+nOffset) );

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// AddClientID. CSN-7534
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::AddClientID( uint32& nOffset, 
								  XMLDoc* pDoc, 
								  uint8*  pGameInBuff )
{
    pGameInBuff[nOffset++] = m_nClientID;

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// LoadLanguageStringTable
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::LoadStringTableForLanguage( XMLDoc* pDoc )
{
    // Get the language from the XML
    String sLang;
	if ( pDoc->GetSingleNodeText( TEXT("//language"), sLang ) )
	{
		m_pServer->loadStringTableForLanguage( sLang );
	}

    // Now we have to set the language for this connection
    m_pParent->SetLanguage( sLang );

    // NOTE: We are always going to return success here.  If the string table
    //   is not loaded, ReturnErrorMessage() will handle it by returning
    //   a generic message back to the client.
    return true;
}

//////////////////////////////////////////////////////////////////////////////
// ReturnLocaleInfo
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::ReturnLocaleInfo( uint8* pOutBuff, uint32 nOutBuffSize )
{
    // Locale Info
    localCurrency localCur;

    // Get local currency info
    int32 chipValues[MAX_CHIPS];

	// Will the Connection even give us a locale?
	if ( !m_pParent->getLocale( &localCur, chipValues, MAX_CHIPS ) )
	{
		return false;
	}

    // Sanity check
	if ( nOutBuffSize == 0 )
	{
		return false;
	}

    // Now, create the return XML message
	XMLDoc doc;
	if ( !doc.StartNew() )
	{
        return false;
    }

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Locale") ) )
	{
		return false;
	}
	
    // Currency
    if ( !doc.AddRootChildNode( TEXT("currency"), String(localCur.int_curr_symbol) ) )
	{
		return false;
	}

    // Currency Symbol
    if ( !doc.AddRootChildNode( TEXT("symbol"), String(localCur.currency_symbol) ) )
	{
		return false;
	}
    
    // Decimal Separator
    if ( !doc.AddRootChildNode( TEXT("decimal_sep"), String(localCur.decimal_sep) ) )
	{
		return false;
	}

    // Thousands Separator
    if ( !doc.AddRootChildNode( TEXT("thousands_sep"), String(localCur.thousands_sep) ) )
	{
		return false;
	}

    // Grouping
    if ( !doc.AddRootChildNode( TEXT("grouping"), uint32(localCur.grouping) ) )
	{
		return false;
	}
    
    // Fractional Digits
    if ( !doc.AddRootChildNode( TEXT("frac_digits"), uint32(localCur.frac_digits) ) )
	{
		return false;
	}
    
    // Currency Symbol Precedes?
    if ( !doc.AddRootChildNode( TEXT("cs_precedes"), bool8(localCur.cs_precedes) ) )
	{
		return false;
	}

    // Separated by Space?
    if ( !doc.AddRootChildNode( TEXT("sep_by_space"), bool8(localCur.sep_by_space) ) )
	{
		return false;
	}

    // Now add chip values
	for(int32 i=0;i<MAX_CHIPS;i++)
	{
		// Create XML node
		String sNode;
		sNode.sprintf( TEXT("chip_value_%d"), i );

        // Add the node
        if ( !doc.AddRootChildNode( sNode, (uint32)chipValues[i] ) )
	    {
		    return false;
	    }
    }
    
    // Add count of chip values
    if ( !doc.AddRootChildNode( TEXT("num_chip_values"), (uint32)MAX_CHIPS ) )
	{
		return false;
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

    return true;
}

//////////////////////////////////////////////////////////////////////////////
// AddCurrencyOutput. CSN-11204
//////////////////////////////////////////////////////////////////////////////
bool8 XMLTranslator::AddCurrencyOutput( XMLDoc *doc )
{
    if ( !doc->AddRootChildNode( TEXT("currency"), m_pParent->getCurrency() ))
	{
		return false;
	}

	return true;
}
